// Graph visualization export functionality
use crate::{Result, CodeNode, CodeEdge, NodeType, EdgeType};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fmt::Write;

/// Supported visualization export formats
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ExportFormat {
    /// Graphviz DOT format
    Graphviz,
    /// D3.js JSON format
    D3Json,
    /// Cytoscape.js JSON format
    Cytoscape,
    /// GEXF format for Gephi
    Gexf,
    /// GraphML format
    GraphML,
}

/// Configuration for graph export
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportConfig {
    /// Export format
    pub format: ExportFormat,
    /// Include node metadata
    pub include_metadata: bool,
    /// Include edge weights
    pub include_weights: bool,
    /// Filter by node types
    pub node_type_filter: Option<Vec<NodeType>>,
    /// Filter by edge types
    pub edge_type_filter: Option<Vec<EdgeType>>,
    /// Maximum number of nodes to export
    pub max_nodes: Option<usize>,
    /// Layout hints for visualization
    pub layout_hints: LayoutHints,
}

/// Layout hints for visualization tools
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LayoutHints {
    /// Suggested layout algorithm
    pub algorithm: String,
    /// Node spacing
    pub node_spacing: f64,
    /// Edge length
    pub edge_length: f64,
    /// Clustering strength
    pub clustering: f64,
}

impl Default for ExportConfig {
    fn default() -> Self {
        Self {
            format: ExportFormat::Graphviz,
            include_metadata: true,
            include_weights: true,
            node_type_filter: None,
            edge_type_filter: None,
            max_nodes: None,
            layout_hints: LayoutHints::default(),
        }
    }
}

impl Default for LayoutHints {
    fn default() -> Self {
        Self {
            algorithm: "force-directed".to_string(),
            node_spacing: 50.0,
            edge_length: 100.0,
            clustering: 0.5,
        }
    }
}

/// Graph visualization exporter
pub struct GraphExporter {
    config: ExportConfig,
}

/// Export result containing the formatted output
#[derive(Debug, Clone)]
pub struct ExportResult {
    /// The exported graph data
    pub content: String,
    /// Content type/MIME type
    pub content_type: String,
    /// Suggested file extension
    pub file_extension: String,
    /// Export statistics
    pub stats: ExportStats,
}

/// Statistics about the export operation
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ExportStats {
    /// Number of nodes exported
    pub nodes_exported: usize,
    /// Number of edges exported
    pub edges_exported: usize,
    /// Export duration in milliseconds
    pub export_time_ms: u64,
    /// Output size in bytes
    pub output_size_bytes: usize,
}

impl GraphExporter {
    /// Create a new graph exporter with default configuration
    pub fn new() -> Self {
        Self {
            config: ExportConfig::default(),
        }
    }

    /// Create a new graph exporter with custom configuration
    pub fn with_config(config: ExportConfig) -> Self {
        Self { config }
    }

    /// Export a graph to the specified format
    pub async fn export_graph(
        &self,
        nodes: &[&dyn CodeNode],
        edges: &[&dyn CodeEdge],
    ) -> Result<ExportResult> {
        let start_time = std::time::Instant::now();

        // Filter nodes and edges based on configuration
        let filtered_nodes = self.filter_nodes(nodes);
        let filtered_edges = self.filter_edges(edges, &filtered_nodes);

        // Generate the export content based on format
        let content = match self.config.format {
            ExportFormat::Graphviz => self.export_graphviz(&filtered_nodes, &filtered_edges)?,
            ExportFormat::D3Json => self.export_d3_json(&filtered_nodes, &filtered_edges)?,
            ExportFormat::Cytoscape => self.export_cytoscape(&filtered_nodes, &filtered_edges)?,
            ExportFormat::Gexf => self.export_gexf(&filtered_nodes, &filtered_edges)?,
            ExportFormat::GraphML => self.export_graphml(&filtered_nodes, &filtered_edges)?,
        };

        let export_time = start_time.elapsed();
        let content_size = content.len();

        Ok(ExportResult {
            content_type: self.get_content_type(),
            file_extension: self.get_file_extension(),
            content,
            stats: ExportStats {
                nodes_exported: filtered_nodes.len(),
                edges_exported: filtered_edges.len(),
                export_time_ms: export_time.as_millis() as u64,
                output_size_bytes: content_size,
            },
        })
    }

    /// Filter nodes based on configuration
    fn filter_nodes<'a>(&self, nodes: &'a [&dyn CodeNode]) -> Vec<&'a dyn CodeNode> {
        let mut filtered = nodes.to_vec();

        // Filter by node type
        if let Some(ref types) = self.config.node_type_filter {
            filtered.retain(|node| types.contains(node.node_type()));
        }

        // Limit number of nodes
        if let Some(max_nodes) = self.config.max_nodes {
            filtered.truncate(max_nodes);
        }

        filtered
    }

    /// Filter edges based on configuration and available nodes
    fn filter_edges<'a>(&self, edges: &'a [&dyn CodeEdge], nodes: &[&dyn CodeNode]) -> Vec<&'a dyn CodeEdge> {
        let node_ids: std::collections::HashSet<_> = nodes.iter().map(|n| n.id()).collect();
        let mut filtered: Vec<&dyn CodeEdge> = edges
            .iter()
            .filter(|edge| {
                node_ids.contains(&edge.source()) && node_ids.contains(&edge.target())
            })
            .copied()
            .collect();

        // Filter by edge type
        if let Some(ref types) = self.config.edge_type_filter {
            filtered.retain(|edge| types.contains(edge.edge_type()));
        }

        filtered
    }

    /// Export to Graphviz DOT format
    fn export_graphviz(&self, nodes: &[&dyn CodeNode], edges: &[&dyn CodeEdge]) -> Result<String> {
        let mut output = String::new();
        
        writeln!(output, "digraph CodeGraph {{")?;
        writeln!(output, "  rankdir=TB;")?;
        writeln!(output, "  node [shape=box, style=filled];")?;
        writeln!(output, "")?;

        // Export nodes
        for node in nodes {
            let node_type = node.node_type();
            let color = self.get_node_color(node_type);
            let label = self.escape_graphviz_label(node.name());

            write!(output, "  \"{}\" [label=\"{}\", fillcolor=\"{}\", tooltip=\"{}\"",
                   node.id(), label, color, format!("{:?}", node_type))?;
            
            if self.config.include_metadata {
                let location = node.location();
                write!(output, ", href=\"{}\"", location.file_path)?;
            }
            
            writeln!(output, "];")?;
        }

        writeln!(output, "")?;

        // Export edges
        for edge in edges {
            let edge_type = edge.edge_type();
            let style = self.get_edge_style(edge_type);
            write!(output, "  \"{}\" -> \"{}\" [style=\"{}\", label=\"{}\"",
                   edge.source(), edge.target(), style, format!("{:?}", edge_type))?;
            
            if self.config.include_weights {
                let weight = edge.metadata().weight;
                write!(output, ", weight={}", weight)?;
            }
            
            writeln!(output, "];")?;
        }

        writeln!(output, "}}")?;
        Ok(output)
    }

    /// Export to D3.js JSON format
    fn export_d3_json(&self, nodes: &[&dyn CodeNode], edges: &[&dyn CodeEdge]) -> Result<String> {
        let mut d3_nodes = Vec::new();
        let mut node_index_map = HashMap::new();

        // Build nodes array
        for (index, node) in nodes.iter().enumerate() {
            node_index_map.insert(node.id(), index);
            
            let node_type = node.node_type();
            let mut node_obj = serde_json::json!({
                "id": node.id().to_string(),
                "name": node.name(),
                "type": format!("{:?}", node_type),
                "group": self.get_node_group(node_type)
            });

            if self.config.include_metadata {
                let location = node.location();
                node_obj["file"] = serde_json::json!(location.file_path);
                node_obj["line"] = serde_json::json!(location.start_line);
            }

            d3_nodes.push(node_obj);
        }

        // Build edges array
        let mut d3_edges = Vec::new();
        for edge in edges {
            if let (Some(&source_idx), Some(&target_idx)) = (
                node_index_map.get(&edge.source()),
                node_index_map.get(&edge.target())
            ) {
                let edge_type = edge.edge_type();
                let mut edge_obj = serde_json::json!({
                    "source": source_idx,
                    "target": target_idx,
                    "type": format!("{:?}", edge_type),
                    "value": 1
                });

                if self.config.include_weights {
                    let weight = edge.metadata().weight;
                    edge_obj["value"] = serde_json::json!(weight);
                }

                d3_edges.push(edge_obj);
            }
        }

        let result = serde_json::json!({
            "nodes": d3_nodes,
            "links": d3_edges,
            "metadata": {
                "format": "d3",
                "version": "1.0",
                "generated": chrono::Utc::now().to_rfc3339(),
                "layout_hints": self.config.layout_hints
            }
        });

        Ok(serde_json::to_string_pretty(&result)?)
    }

    /// Export to Cytoscape.js JSON format
    fn export_cytoscape(&self, nodes: &[&dyn CodeNode], edges: &[&dyn CodeEdge]) -> Result<String> {
        let mut elements = Vec::new();

        // Add nodes
        for node in nodes {
            let node_type = node.node_type();
            let mut node_data = serde_json::json!({
                "id": node.id().to_string(),
                "label": node.name(),
                "type": format!("{:?}", node_type)
            });

            if self.config.include_metadata {
                let location = node.location();
                node_data["file"] = serde_json::json!(location.file_path);
                node_data["line"] = serde_json::json!(location.start_line);
            }

            elements.push(serde_json::json!({
                "data": node_data,
                "classes": format!("node-{}", format!("{:?}", node_type).to_lowercase())
            }));
        }

        // Add edges
        for edge in edges {
            let edge_type = edge.edge_type();
            let mut edge_data = serde_json::json!({
                "id": format!("{}_{}", edge.source(), edge.target()),
                "source": edge.source().to_string(),
                "target": edge.target().to_string(),
                "type": format!("{:?}", edge_type)
            });

            if self.config.include_weights {
                let weight = edge.metadata().weight;
                edge_data["weight"] = serde_json::json!(weight);
            }

            elements.push(serde_json::json!({
                "data": edge_data,
                "classes": format!("edge-{}", format!("{:?}", edge_type).to_lowercase())
            }));
        }

        let result = serde_json::json!({
            "elements": elements,
            "style": self.get_cytoscape_style(),
            "layout": {
                "name": self.config.layout_hints.algorithm,
                "nodeSpacing": self.config.layout_hints.node_spacing,
                "edgeLength": self.config.layout_hints.edge_length
            }
        });

        Ok(serde_json::to_string_pretty(&result)?)
    }

    /// Export to GEXF format for Gephi
    fn export_gexf(&self, nodes: &[&dyn CodeNode], edges: &[&dyn CodeEdge]) -> Result<String> {
        let mut output = String::new();
        
        writeln!(output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
        writeln!(output, "<gexf xmlns=\"http://www.gexf.net/1.2draft\" version=\"1.2\">")?;
        writeln!(output, "  <meta lastmodifieddate=\"{}\">\n    <creator>CodeGraph-RT</creator>\n  </meta>", 
                 chrono::Utc::now().format("%Y-%m-%d"))?;
        writeln!(output, "  <graph mode=\"static\" defaultedgetype=\"directed\">")?;

        // Attributes
        if self.config.include_metadata {
            writeln!(output, "    <attributes class=\"node\">")?;
            writeln!(output, "      <attribute id=\"0\" title=\"type\" type=\"string\"/>")?;
            writeln!(output, "      <attribute id=\"1\" title=\"file\" type=\"string\"/>")?;
            writeln!(output, "    </attributes>")?;
        }

        // Nodes
        writeln!(output, "    <nodes>")?;
        for node in nodes {
            let node_type = node.node_type();
            write!(output, "      <node id=\"{}\" label=\"{}\">", node.id(), self.escape_xml(node.name()))?;

            if self.config.include_metadata {
                writeln!(output, "\n        <attvalues>")?;
                writeln!(output, "          <attvalue for=\"0\" value=\"{}\"/>", format!("{:?}", node_type))?;
                let location = node.location();
                writeln!(output, "          <attvalue for=\"1\" value=\"{}\"/>", self.escape_xml(&location.file_path))?;
                writeln!(output, "        </attvalues>")?;
                writeln!(output, "      </node>")?;
            } else {
                writeln!(output, "</node>")?;
            }
        }
        writeln!(output, "    </nodes>")?;

        // Edges
        writeln!(output, "    <edges>")?;
        for (i, edge) in edges.iter().enumerate() {
            let edge_type = edge.edge_type();
            write!(output, "      <edge id=\"{}\" source=\"{}\" target=\"{}\" label=\"{}\"",
                   i, edge.source(), edge.target(), format!("{:?}", edge_type))?;
            
            if self.config.include_weights {
                let weight = edge.metadata().weight;
                write!(output, " weight=\"{}\"", weight)?;
            }
            
            writeln!(output, "/>")?;
        }
        writeln!(output, "    </edges>")?;
        writeln!(output, "  </graph>")?;
        writeln!(output, "</gexf>")?;

        Ok(output)
    }

    /// Export to GraphML format
    fn export_graphml(&self, nodes: &[&dyn CodeNode], edges: &[&dyn CodeEdge]) -> Result<String> {
        let mut output = String::new();
        
        writeln!(output, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>")?;
        writeln!(output, "<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">")?;
        
        // Key definitions
        writeln!(output, "  <key id=\"name\" for=\"node\" attr.name=\"name\" attr.type=\"string\"/>")?;
        writeln!(output, "  <key id=\"type\" for=\"node\" attr.name=\"type\" attr.type=\"string\"/>")?;
        writeln!(output, "  <key id=\"file\" for=\"node\" attr.name=\"file\" attr.type=\"string\"/>")?;
        writeln!(output, "  <key id=\"edge_type\" for=\"edge\" attr.name=\"type\" attr.type=\"string\"/>")?;
        writeln!(output, "  <key id=\"weight\" for=\"edge\" attr.name=\"weight\" attr.type=\"double\"/>")?;
        
        writeln!(output, "  <graph id=\"CodeGraph\" edgedefault=\"directed\">")?;

        // Nodes
        for node in nodes {
            let node_type = node.node_type();
            writeln!(output, "    <node id=\"{}\">\n      <data key=\"name\">{}</data>\n      <data key=\"type\">{}</data>",
                     node.id(), self.escape_xml(node.name()), format!("{:?}", node_type))?;
            
            if self.config.include_metadata {
                let location = node.location();
                writeln!(output, "      <data key=\"file\">{}</data>", self.escape_xml(&location.file_path))?;
            }
            
            writeln!(output, "    </node>")?;
        }

        // Edges
        for (i, edge) in edges.iter().enumerate() {
            let edge_type = edge.edge_type();
            writeln!(output, "    <edge id=\"e{}\" source=\"{}\" target=\"{}\">", i, edge.source(), edge.target())?;
            writeln!(output, "      <data key=\"edge_type\">{}</data>", format!("{:?}", edge_type))?;
            
            if self.config.include_weights {
                let weight = edge.metadata().weight;
                writeln!(output, "      <data key=\"weight\">{}</data>", weight)?;
            }
            
            writeln!(output, "    </edge>")?;
        }

        writeln!(output, "  </graph>")?;
        writeln!(output, "</graphml>")?;

        Ok(output)
    }

    // Helper methods
    fn get_content_type(&self) -> String {
        match self.config.format {
            ExportFormat::Graphviz => "text/vnd.graphviz".to_string(),
            ExportFormat::D3Json | ExportFormat::Cytoscape => "application/json".to_string(),
            ExportFormat::Gexf | ExportFormat::GraphML => "application/xml".to_string(),
        }
    }

    fn get_file_extension(&self) -> String {
        match self.config.format {
            ExportFormat::Graphviz => "dot".to_string(),
            ExportFormat::D3Json => "json".to_string(),
            ExportFormat::Cytoscape => "cyjs".to_string(),
            ExportFormat::Gexf => "gexf".to_string(),
            ExportFormat::GraphML => "graphml".to_string(),
        }
    }

    fn get_node_color(&self, node_type: &NodeType) -> &'static str {
        match node_type {
            NodeType::Function => "lightblue",
            NodeType::Class => "lightgreen",
            NodeType::Interface => "lightcyan",
            NodeType::Struct => "lightsteelblue",
            NodeType::Enum => "lightpink",
            NodeType::Variable => "lightyellow",
            NodeType::Constant => "gold",
            NodeType::Import => "lightcoral",
            NodeType::Export => "lightsalmon",
            NodeType::Module => "lightgray",
            NodeType::Package => "silver",
            NodeType::Namespace => "gainsboro",
            NodeType::Call => "lavender",
            NodeType::Reference => "mistyrose",
            NodeType::Assignment => "peachpuff",
            NodeType::Declaration => "wheat",
            NodeType::Comment => "lightgoldenrodyellow",
            NodeType::Unknown => "white",
        }
    }

    fn get_node_group(&self, node_type: &NodeType) -> u32 {
        match node_type {
            NodeType::Function => 1,
            NodeType::Class => 2,
            NodeType::Interface => 2,
            NodeType::Struct => 2,
            NodeType::Enum => 2,
            NodeType::Variable => 3,
            NodeType::Constant => 3,
            NodeType::Import => 4,
            NodeType::Export => 4,
            NodeType::Module => 5,
            NodeType::Package => 5,
            NodeType::Namespace => 5,
            NodeType::Call => 6,
            NodeType::Reference => 6,
            NodeType::Assignment => 7,
            NodeType::Declaration => 7,
            NodeType::Comment => 8,
            NodeType::Unknown => 9,
        }
    }

    fn get_edge_style(&self, edge_type: &EdgeType) -> &'static str {
        match edge_type {
            EdgeType::Calls => "solid",
            EdgeType::Inherits => "dashed",
            EdgeType::Implements => "dashed",
            EdgeType::Imports => "bold",
            EdgeType::References => "dotted",
            EdgeType::Defines => "solid",
            EdgeType::Contains => "solid",
            EdgeType::DependsOn => "dashed",
            EdgeType::Uses => "dotted",
            EdgeType::ControlFlow => "bold",
            EdgeType::DataFlow => "solid",
            EdgeType::TypeRelation => "dashed",
            EdgeType::Unknown => "dotted",
        }
    }

    fn get_cytoscape_style(&self) -> serde_json::Value {
        serde_json::json!([
            {
                "selector": "node",
                "style": {
                    "background-color": "#666",
                    "label": "data(label)",
                    "text-valign": "center",
                    "text-halign": "center"
                }
            },
            {
                "selector": "edge",
                "style": {
                    "width": 2,
                    "line-color": "#ccc",
                    "target-arrow-color": "#ccc",
                    "target-arrow-shape": "triangle",
                    "curve-style": "bezier"
                }
            }
        ])
    }

    fn escape_graphviz_label(&self, text: &str) -> String {
        text.replace('\\', "\\\\")
            .replace('"', "\\\"")
            .replace('\n', "\\n")
    }

    fn escape_xml(&self, text: &str) -> String {
        text.replace('&', "&amp;")
            .replace('<', "&lt;")
            .replace('>', "&gt;")
            .replace('"', "&quot;")
            .replace('\'', "&apos;")
    }
}

impl Default for GraphExporter {
    fn default() -> Self {
        Self::new()
    }
}
